/** \file    tga.h
  * \brief   Input/output of images in uncompressed TGA file format.
  * \date    2003
  * \author  Fedor Chelnokov
  * \version $Header: /home/cscom1r4/cvsroot/cs/include/imglib/tga.h,v 1.7 2006/02/26 16:49:55 cscom1r4 Exp $
  */

#ifndef _tga_h_
#define _tga_h_

#include <fstream>
#include <biostream>
#include <functional>

#include "imglib/image.h"
#include "imglib/color.h"
#include "imglib/io_error.h"
#include "imglib/io_types.h"
#include "imglib/type_traits.h"

namespace img {

namespace tga {

///////////
// types //
///////////

typedef xtd::int8u byte;
typedef BGRA<byte> color;
typedef BGR <byte> colorx;

#pragma pack (push, 1)

struct file_header {
	byte		id_length;
	byte		colormap_type;
    byte		image_type;
	xtd::int16u colormap_index;
	xtd::int16u colormap_length;
    byte		colormap_size;
	xtd::int16u x_origin;
    xtd::int16u y_origin;
    xtd::int16u width;
    xtd::int16u height;
	byte		bits_per_pixel;
    byte		attributes;
};

#pragma pack (pop)	 

struct grey_pixel
{
	byte val;

	friend xtd::bistream & operator >> (xtd::bistream & bin, grey_pixel & gr) {
		bin >> gr.val;
		return bin;
	}
	operator byte() const
		{ return val; }
	operator colorx() const 
		{ return colorx(val, val, val); }
	operator color() const 
		{ return color(val, val, val, 0); }
};

struct short_pixel
{
private:
	union u {
		unsigned short v;
		struct { unsigned short b:5, g:5, r:5; } s;
	};
public:
	unsigned short v;

	short_pixel & operator = (const colorx & rhs) {
		u x;
		//31 * rhs.b / 255; makes image darker
		x.s.b = rhs.b >> 3;
		x.s.g = rhs.g >> 3;
		x.s.r = rhs.r >> 3;
		v = x.v;
		return *this;
	}

	friend xtd::bistream & operator >> (xtd::bistream & bin, short_pixel & sh) {
		bin >> sh.v;
		return bin;
	}
	operator unsigned short() const
		{ return v; }
	operator colorx() const {
		u x;
		x.v = v;
		return colorx(
			255 * x.s.b / 31,
			255 * x.s.g / 31,
			255 * x.s.r / 31);
	}
	operator color() const {
		u x;
		x.v = v;
		return color(
			255 * x.s.b / 31,
			255 * x.s.g / 31,
			255 * x.s.r / 31,
			0);
	}
};

struct long_pixel
{
	byte b, g, r;
	friend xtd::bistream & operator >> (xtd::bistream & bin, long_pixel & lg) {
		//bin >> lg.b >> lg.g >> lg.r;
		bin.read(reinterpret_cast<char *>(&lg), sizeof(lg)); //2 times faster
		return bin;
	}
	operator colorx() const 
		{ return colorx(b, g, r); }
	operator color() const 
		{ return color(b, g, r, 0); }
};

struct huge_pixel : public color
{
	friend xtd::bistream & operator >> (xtd::bistream & bin, huge_pixel & h) {
		//bin >> h.b >> h.g >> h.r >> h.a;
		bin.read(reinterpret_cast<char *>(&h), sizeof(h)); //3 times faster
		return bin;
	}
};

/////////////
// palette //
/////////////

class palette : public std::unary_function<byte, colorx> {
private:
	std::vector<colorx> _entries;
	byte _colormap_size;
public:
	palette(unsigned int count, byte colormap_size)
		: _entries(count), _colormap_size(colormap_size) { }
	unsigned int size() const
		{ return _entries.size(); }
	friend xtd::bistream & operator >> (xtd::bistream & bin, palette & pal) {
		switch (pal._colormap_size) {
		case 8:
        default:
			pal.read<grey_pixel>(bin);
			break;
        case 15:
        case 16:
			pal.read<short_pixel>(bin);
			break;
        case 24:
        case 32:
			pal.read<long_pixel>(bin);
			break;
		};
		return bin;
	}
	colorx operator () (unsigned index) const {
		if (index >= _entries.size())
			throw bad(bad::palette_index, index);
		return _entries[index];
	}

private:
	template <class P>
	void read(xtd::bistream & bin) {
		for (int i = 0; i != size(); ++i) {
			P p;
			bin >> p;
			_entries[i] = p;
		}
	}
};

__declspec(noreturn) inline void read_throw(const char * filename = "") {
	throw io_error("could not open TGA image for reading or read error", filename);
}

//////////
// read //
//////////

///generic read from file
template <class I> 
void read(const char * filename, I & img) 
{
	try {

		std::filebuf file;

		#if defined __GNUC__
		//buffer may increase IO speed in some STL implementations
		char buffer[16*1024];
		file.pubsetbuf(buffer, 16*1024);
		#endif 

		file.open(filename, std::ios::in | std::ios::binary);

		xtd::bistream bin(&file);
		bin.exceptions(std::ios::badbit|std::ios::failbit);

		read(bin, img);

		file.close();

	}
	catch (io_error & e) {
		if (e.filename().empty())
			e.filename(filename);
		throw;
	}
	catch (const std::exception &) {
		read_throw(filename);
	}
}

template <class I> 
inline void read(const std::string & path, I & img) 
	{ read(path.c_str(), img); }

class reader {
public:
	xtd::bistream & bin;
	file_header bf;
	palette * pal;
	
	reader(xtd::bistream & _bin) : bin(_bin), pal(0)
	{
		bin.get(bf);

		if (bf.image_type == 0 || bf.image_type > 11)
			throw unsupported(unsupported::file_type, bf.image_type);
		//if (compression())
		//	throw unsupported(unsupported::compression, bf.image_type);
		if ((bf.attributes & 0xc0) != 0)
			throw unsupported(unsupported::interleaving);

		bool palette_exists = bf.colormap_type != 0;

		switch (bf.bits_per_pixel) {
		case 8:
		default:
			if (!palette_exists)
				throw unsupported(unsupported::no_palette);
		case 15:
		case 16:
			break;
		case 24:
		case 32:
			if (palette_exists)
				throw bad(bad::palette);
		}

		if (bf.id_length != 0) {
			//skip comment
			bin.seekg(bf.id_length, xtd::bistream::cur);
		}

		if (palette_exists) {
			pal = new palette(bf.colormap_length, bf.colormap_size);
			bin >> *pal;
		}
	}

	~reader() 
		{ delete pal; }

	bool compression() const
		{ return bf.image_type > 8; }

	template <class I>
	void prepare(I & img) {
		order_type order = (((bf.attributes & 0x20) >> 5) == 0) ? 
			bottom_up : top_down;
		img.order(order);
		img.resize(bf.width, bf.height, 1); //'1' is a recent change!
	}

	template <class I>
	void contents(I & img) {
		prepare(img);
		bin.read(reinterpret_cast<char *>(img.data()), img.bytes());
	}
};

typedef grey_pixel in8bit_t;
const in8bit_t in8bit = {};

typedef short_pixel in15bit_t;
const in15bit_t in15bit = {};

typedef long_pixel in24bit_t;
const in24bit_t in24bit = {};

typedef huge_pixel in32bit_t;
const in32bit_t in32bit;

template <class B, class I> 
void read_contents(B, I & img, reader & r);

template <class I> 
void read_contents(in8bit_t, I & img, reader & r) 
{
	image<byte> tmp;
	r.contents(tmp);
	img.copy_attributes(tmp);
	img = modify(tmp, *r.pal);
}

template <class I> 
void read_contents(in15bit_t, I & img, reader & r) 
{
	if (!r.pal) {
		simple_image<short_pixel> tmp;
		r.contents(tmp);
		img.copy_attributes(tmp);
		img = tmp;
	}
	else {
		image<unsigned short> tmp;
		r.contents(tmp);
		img.copy_attributes(tmp);
		img = modify(tmp, *r.pal);
	}
}

template <class I> 
inline void read_contents(in24bit_t, I & img, reader & r) 
{
	image<colorx> tmp;
	read_contents(in24bit, tmp, r);
	img.copy_attributes(tmp);
	img = tmp;
}

template <> 
inline void read_contents(in24bit_t, image<colorx> & img, reader & r) 
{
	r.contents(img);
}

template <class I> 
inline void read_contents(in32bit_t, I & img, reader & r) 
{
	image<color> tmp;
	read_contents(in32bit, tmp, r);
	img.copy_attributes(tmp);
	img = tmp;
}

template <> 
inline void read_contents(in32bit_t, image<color> & img, reader & r) 
{
	r.contents(img);
}

template <class E, class I, class D> 
void read_rle_contents_impl(E encoding_element, I & img, reader & r, const D & decoder) 
{
	r.prepare(img);
	typename I::iterator i = img.begin();
	while (i != img.end()) {
		byte c;
		r.bin >> c;
		unsigned count = c & 0x7f;
		if (count == c) {
			//raw packet
			for (unsigned j = 0; j <= count; ++j) {
				r.bin >> encoding_element;
				*i = decoder(encoding_element);
				if (++i == img.end())
					break;
			}
		}
		else {
			//runlength packet
			r.bin >> encoding_element;
			typename I::elem_type v = decoder(encoding_element);
			for (unsigned j = 0; j <= count; ++j) {
				*i = v;
				if (++i == img.end())
					break;
			}
		}
	}
	if (img.order() == bottom_up)
		img.flip_order();
}

template <class I> 
void read_rle_contents_impl(in24bit_t, I & img, reader & r, const palette &) 
{
	throw unsupported(unsupported::file_type);
}

template <class I> 
void read_rle_contents_impl(in32bit_t, I & img, reader & r, const palette &) 
{
	throw unsupported(unsupported::file_type);
}

template <class E>
struct not_change {
	E operator () (E e) const
		{ return e; }
};

template <class E, class I> 
void read_rle_contents(E encoding_element, I & img, reader & r)
{
	if (r.pal)
		read_rle_contents_impl(encoding_element, img, r, *r.pal);
	else
		read_rle_contents_impl(encoding_element, img, r, not_change<E>());
}

///selects between raw data reading and rle decompression
template <class E, class I> 
void read_contents_dispatcher(E encoding_element, I & img, reader & r)
{
	if (r.compression())
		read_rle_contents(encoding_element, img, r);
	else
		read_contents(encoding_element, img, r);
}

///generic read from binary stream
template <class I> 
void read(xtd::bistream & bin, I & img) 
{
	reader r(bin);

	switch (r.bf.bits_per_pixel) {
	case 8:
	default:
		read_contents_dispatcher(in8bit, img, r);
		return;
	case 15:
	case 16:
		read_contents_dispatcher(in15bit, img, r);
		return;
	case 24:
		read_contents_dispatcher(in24bit, img, r);
		return;
	case 32:
		read_contents_dispatcher(in32bit, img, r);
		return;
	};
}

//////////
// ping //
//////////

inline bool ping(const std::string & path, file_info * info = 0)
{
	try {
		std::filebuf file;
		file.open(path.c_str(), std::ios::in | std::ios::binary);
		xtd::bistream bin(&file);
		bin.exceptions(std::ios::badbit|std::ios::failbit);
		reader r(bin);

		if (info) {
			info->path = path;
			info->format = "TGA";
			info->dim = size_extent(r.bf.width, r.bf.height);
			info->bit_count = r.bf.bits_per_pixel;
			info->compressed = r.bf.image_type <= 8;
		}
		return true;
	}
	catch (const std::exception &) {
		return false;
	}
}

///////////
// write //
///////////

class writer {
public:
	xtd::bostream & bout;
	file_header bf;
	
	writer(xtd::bostream & _bout) : bout(_bout)
	{
		memset(&bf, 0, sizeof(file_header));
		bf.image_type = 2; //rgb
	}

	template <class I>
	void header(I & img) {
		bf.width = img.width();
		bf.height = img.height();
		bf.attributes = (img.order() == top_down) ? 0x20 : 0;
		bout.put(bf);
	}

	template <class I>
	void body(I & img) {
		bout.write(reinterpret_cast<const char *>(img.data()), img.bytes());
	}

	template <class I>
	void everything(I & img) {
		header(img);
		body(img);
	}
};

template <class I> 
struct write_to_stream {
	//template typedef is allowed only inside a class
	typedef void (*func)(xtd::bostream &, const I &);
};

///generic write to file
template <class I> 
size_t write(const char * filename, const I & img, typename write_to_stream<I>::func f) 
{
	std::filebuf file;

	#if defined __GNUC__
	//buffer may increase IO speed in some STL implementations
	char buffer[16*1024];
	file.pubsetbuf(buffer, 16*1024);
	#endif 

	file.open(filename, std::ios::out | std::ios::binary);

	xtd::bostream bout(&file);
	bout.exceptions(std::ios::badbit|std::ios::failbit);

	f(bout, img);

	return bout.tellp();
}

//////////////////
// write 32 bit //
//////////////////

template <class I> 
inline void write32bit(xtd::bostream & bout, const I & img) 
{
	image<color> tmp(img.width(), img.height());
	tmp.order(img.order());
	tmp = img;
	write32bit(bout, tmp);
}

template <> 
inline void write32bit(xtd::bostream & bout, const image<color> & img) 
{
	writer w(bout);
	w.bf.bits_per_pixel = 32;

	unsigned a = img.alignment();
	if (a == 4 || a <= 2) //it is equivalent to no alignment for 4-byte long type
		w.everything(img);
	else {
		image<color> tmp(img.width(), img.height());
		tmp.order(img.order());
		tmp = img;
		w.everything(tmp);
	}
}

template <class I> 
inline size_t write32bit(const char * filename, const I & img) {	
	typename write_to_stream<I>::func f = write32bit;
	return write(filename, img, f);
}

//////////////////
// write 24 bit //
//////////////////

template <class I> 
inline void write24bit(xtd::bostream & bout, const I & img) 
{
	image<colorx> tmp(img.width(), img.height(), 1); //'1' is a recent change!
	tmp.order(img.order());
	tmp = img;
	write24bit(bout, tmp);
}

template <> 
inline void write24bit(xtd::bostream & bout, const image<colorx> & img) 
{
	writer w(bout);
	w.bf.bits_per_pixel = 24;

	unsigned a = img.alignment();
	if (a == 3 || a <= 1) //it is equivalent to no alignment for 3-byte long type
		w.everything(img);
	else {
		image<colorx> tmp(img.width(), img.height(), 1); //'1' is a recent change!
		tmp.order(img.order());
		tmp = img;
		w.everything(tmp);
	}
}

template <class I> 
inline size_t write24bit(const char * filename, const I & img) {	
	typename write_to_stream<I>::func f = write24bit;
	return write(filename, img, f);
}

//////////////////
// write 15 bit //
//////////////////

template <class I> 
inline void write15bit(xtd::bostream & bout, const I & img) 
{
	simple_image<short_pixel> tmp(img.width(), img.height());
	tmp.order(img.order());
	tmp = img;

	writer w(bout);
	w.bf.bits_per_pixel = 16; //15 is not understood by ACDSee
	w.everything(tmp);
}

template <class I> 
inline size_t write15bit(const char * filename, const I & img) {	
	typename write_to_stream<I>::func f = write15bit;
	return write(filename, img, f);
}

} //namespace tga
} //namespace img

#endif //_tga_h_
